//
// Created by 13562 on 2024/1/6.
//

#include "StreamFactory.h"
#include <regex>
#include "rapidjson/document.h"
#include <fstream>
#include "utils.h"
#include "boost/algorithm/string/split.hpp"
#include "boost/algorithm/string.hpp"

using namespace cv;

void StreamFactory::init(const string &configFile) {
//	加载配置
	loadConfig(configFile);
//	读取文件名到文件列表
	fileNames = getFiles(leftFrameDirectory, fileNamePattern, fileSuffix);
//	同步文件数目
	frameCounts = fileNames.size();
	currFrameIndex = 0;

	if (timeMode == APPROX_TIME) {
		// 设置起始时间点
		tm timeinfo{};
		strptime(timeBaseStr.c_str(), timeFormat.c_str(), &timeinfo);
		time_t ts = mktime(&timeinfo);
		timeBase = chrono::system_clock::from_time_t(ts);
		cout << "   Time mode set to approx-time" << endl;
		cout << "   Time origin set to: " << ts << " (ts) " << put_time(&timeinfo, "%Y-%m-%d %H:%M:%S") << " (format)" << endl;
	} else if (timeMode == SYNCED_TIME) {
		// 使用同步时间
		// 读取时间戳文件
		loadTimestamps(tsFileUrl);
		cout << "   Time mode set to synced-time" << endl;
		cout << "   Using timestamps at " << tsFileUrl << endl;
	}
//	读取图片信息
	read(currLeftFrame, getFileUrl(leftFrameDirectory, fileNames.at(0)));
	cout << "   Image size: " << currLeftFrame.cols << " (w) " << currLeftFrame.rows << " (h) " << endl;
//	使能
	this->enable();
}

vector<string> StreamFactory::getFiles(const string &directory, const string &namePattern, const string &suffix) {
    vector<string> resultFiles;
/* DIR is the data type of directory stream objects.
   The actual structure is opaque to users.  */
    DIR *dir = opendir(directory.c_str());
    if (dir == nullptr) {
        cerr << "No such directory: " << directory << endl;
        return resultFiles;
    }
//  存储目录下内容的各种属性
    struct dirent* d_ent = nullptr;
//  遍历目录下的内容，内容的属性到d_ent中
    while ((d_ent = readdir(dir)) != nullptr) {
        if (strcmp(d_ent->d_name, ".") == 0 || strcmp(d_ent->d_name, "..") == 0) continue;
//      DT_DIR(enum) 为目录类型
        regex rx(namePattern + suffix);
        if (d_ent->d_type != DT_DIR && regex_match(d_ent->d_name, rx)) {
            resultFiles.emplace_back(d_ent->d_name);
        }
    }
    sort(resultFiles.begin(), resultFiles.end());
    closedir(dir);
    return resultFiles;
}

stringstream StreamFactory::readFile(const std::string &file) {
//	定义文件输入流
	ifstream inputFS(file);
	if (!inputFS.good()) {
		stringstream ss;
		ss << "failed";
		return ss;
	}
//	获取配置全文
	stringstream ss;
	char ch;
	while (!inputFS.eof()) {
		inputFS.get(ch);
		ss << ch;
	}
	inputFS.close();
	return ss;
}

void StreamFactory::loadConfig(const string &configFile) {
	string configContent = readFile(configFile).str();
	if (configContent == "failed") {
		cerr << "Failed to open config file at " << configFile << endl;
		return;
	}
//  使用rapidjson解析
	rapidjson::Document dom;
	dom.Parse(configContent.c_str());
	if (dom.HasParseError()) {
		cerr << "Rapidjson parse error." << endl;
	}
//	赋值到成员变量
	fileSuffix = getStringFrom(dom, "file-suffix");
	leftFrameDirectory = getStringFrom(dom, "left-frame-directory");
	rightFrameDirectory = getStringFrom(dom, "right-frame-directory");
	fileNamePattern = getStringFrom(dom, "file-name-pattern");
	startIndex = getIntegerFrom(dom, "start");
	endIndex = getIntegerFrom(dom, "end");

	string tm = getStringFrom(dom, "time-mode");
	rapidjson::Value& node = getObjectFrom(dom, tm);
	if (tm == "approx-time") {
		timeMode = APPROX_TIME;
		timeBaseStr = getStringFrom(node, "base");
		timeFormat = getStringFrom(node, "pattern");
		fps = getDoubleFrom(node, "fps");
	} else if (tm == "synced-time") {
		timeMode = SYNCED_TIME;
		tsFileUrl = getStringFrom(node, "timestamps-file");
		timeFormat = getStringFrom(node, "pattern");
		tsPrecision = getIntegerFrom(node, "precision");
		timeShift = getIntegerFrom(node, "time-shift");
	}
	dpf = 1.0/fps;
}


void StreamFactory::loadTimestamps(const std::string &tsFile) {
	stringstream tsStream = readFile(tsFile);
	if (tsStream.str() == "failed") {
		cerr << "Failed to open timestamp file at " << tsFile << endl;
		return;
	}

	while (!tsStream.eof()) {
		string line;
		getline(tsStream, line);
		if (line.empty()) break;
		vector<string> segments;
		boost::split(segments, line, boost::is_any_of("."), boost::token_compress_on);
		tm timeinfo{};
		strptime(segments[0].c_str(), timeFormat.c_str(), &timeinfo);
		time_t ts = mktime(&timeinfo);
		if (segments.size() > 1) {
			double frac_secs = stod(segments[1])*pow(10, -tsPrecision);
			long frac_nsecs = long(frac_secs*1e9);
			timestamps.emplace_back(ts*long(1e9) + frac_nsecs + int64_t(timeShift*3600e9));
		} else {
			timestamps.emplace_back(ts*long(1e9) + int64_t(timeShift*3600e9));
		}
	}
	fps = double(timestamps.size())/(double(timestamps.back() - timestamps.front())*1e-9);
	dpf = 1.0/fps;
}

inline string StreamFactory::getFileUrl(const string &directory, const string &fileName) {
	unsigned int dirLen = directory.size();
	if (directory.at(dirLen - 1) == '/') {
		return directory + fileName;
	} else {
		return directory + "/" + fileName;
	}
}

void StreamFactory::readFrame(unsigned long index) {
	const string leftUrl = getFileUrl(leftFrameDirectory, fileNames.at(index));
	const string rightUrl = getFileUrl(rightFrameDirectory, fileNames.at(index));
	read(currLeftFrame, leftUrl);
	read(currRightFrame, rightUrl);
}

long StreamFactory::setTimestamp() {
	long ts;
	if (timeMode == APPROX_TIME) {
//		    由时间间隔计算timestamp
		auto duration = chrono.timeCostMilli();
		chrono::system_clock::time_point currRefTime = timeBase + duration;
		chrono::duration<uint64_t, nano> tsm = chrono::duration_cast<chrono::duration<uint64_t, nano>>(currRefTime.time_since_epoch());
		timestamp_nanos = tsm.count();
		ts = chrono::system_clock::to_time_t(currRefTime);
	} else if (timeMode == SYNCED_TIME) {
//			使用提供的timestamp
		timestamp_nanos = timestamps[currFrameIndex];
		ts = long(double(timestamp_nanos) * 1e-9);
	}
	return ts;
}

inline void StreamFactory::read(Mat &frame, const string &imageFile) {
	frame = imread(imageFile, IMREAD_COLOR);
}

void StreamFactory::showFrames(const vector<string>& winNames) {
	vector<pair<const string&, Mat&>> windows;
	if (winNames.size() != 2) {
		cerr << "Have not specify windows name." << endl;
		return;
	}
	windows.emplace_back(winNames[0], currLeftFrame);
	windows.emplace_back(winNames[1], currRightFrame);
//	创建图像显示线程
	thread showThread(showImage, windows);
	showThread.detach();
	future<ShowThreadReturn> threadRst = showThreadReturn.get_future();
}

void StreamFactory::updateImage() {
	while(currFrameIndex <= endIndex) {
		if (EXIT_SIGNAL) break;
		if (timeMode == APPROX_TIME) {
			if ((chrono.timeCostMilli() - timeCost) < chrono::milliseconds(uint(dpf*1000))) continue;
		} else if (timeMode == SYNCED_TIME) {
			if (currFrameIndex == 0) {
				currFrameIndex = 1;
				timeCost = chrono::duration_cast<chrono::milliseconds>(chrono::nanoseconds(timestamps[1] - timestamps[0]));
			}
			if ((chrono.timeCostMilli() - timeCost) < std::chrono::duration_cast<chrono::milliseconds>(
					chrono::nanoseconds(timestamps[currFrameIndex] - timestamps[currFrameIndex - 1])
					)) continue;
		}

//		得到从init起算的时间间隔
		timeCost = chrono.timeCostMilli();
		mtx.lock();
//      读取图片到当前帧
		readFrame(currFrameIndex);

		long ts = setTimestamp();

//		用于输出的格式化时间
		struct tm timeinfo = *gmtime(&ts);
		if (echo) {
			cout << "\nimg: " << setw(6) << right << setfill('_') << currFrameIndex << "/" << frameCounts - 1 <<
			     setw(6) << " TimeTick: " << setw(8) << chrono.timeCostMilli().count() << " ms" <<
			     " Timestamp(s): " << fixed << setprecision(9) << setw(19) << double(timestamp_nanos) * 1e-9 <<
			     setfill(' ') << " FTime: " << setw(20) << put_time(&timeinfo, "%Y-%m-%d %H:%M:%S");
		}
		currFrameIndex++;
		mtx.unlock();
	}
	if (currFrameIndex == frameCounts - 1) {
		cout << "\nLast frame loaded." << endl;
	}
	cout << "\nReading thread ended." << endl;
	this->disable();
}

StreamFactory::ShowThreadReturn StreamFactory::showImage(const vector<pair<const string&, cv::Mat&>>& windows) {
	uint keyValue;
	bool emptyFlag = false;
	for (auto& window : windows) {
		namedWindow(window.first);
	}
	while (true) {
		for (auto& window : windows) {
			emptyFlag = emptyFlag || window.second.empty();
		}
		if (emptyFlag) break;
		for (auto& window : windows) {
			imshow(window.first, window.second);
		}
		keyValue = waitKey(1);
		this_thread::sleep_for(std::chrono::milliseconds(20));
		if ((char) keyValue == 'q') {
			cout << " Manually quited." << endl;
			break;
		}
	}
	if (emptyFlag) {
		cout << " Quited by empty frame." << endl;
	}
	destroyAllWindows();
	return ((char) keyValue == 'q') ? MANUALLY : AUTOMATICALLY;
}


void StreamFactory::clearFrames() {
	currLeftFrame.release();
	currRightFrame.release();
}


void StreamFactory::enable() {
	this->enabled = true;
//	归零计时器
	chrono.reset();
//	置空图像
	currLeftFrame = Mat(100, 100, CV_8UC3, Scalar(100, 0, 0));
	currRightFrame = Mat(100, 100, CV_8UC3, Scalar(0, 0, 100));
//	设置起始帧
	currFrameIndex = startIndex;
//	设置终止帧
	if (endIndex == 0) {
		endIndex = frameCounts - 1;
	}
//	创建图像更新线程
	thread imageUpdateThread(&StreamFactory::updateImage, this);
	imageUpdateThread.detach();
}


void StreamFactory::disable() {
	this->enabled = false;
	clearFrames();
}

/**============================================== Getters ============================================================*/

const string &StreamFactory::getLeftFrameDirectory() const {
	return leftFrameDirectory;
}

const string &StreamFactory::getRightFrameDirectory() const {
	return rightFrameDirectory;
}

const string &StreamFactory::getFileNamePattern() const {
	return fileNamePattern;
}

const string &StreamFactory::getFileSuffix() const {
	return fileSuffix;
}

const Mat &StreamFactory::getCurrLeftFrame() const {
	return currLeftFrame;
}

const Mat &StreamFactory::getCurrRightFrame() const {
	return currRightFrame;
}

uint64_t StreamFactory::getCurrTimestamp() const {
	return timestamp_nanos;
}

bool StreamFactory::isEnabled() const {
	return enabled;
}

unsigned long StreamFactory::getFrameCounts() const {
	return frameCounts;
}

unsigned long StreamFactory::getCurrFrameIndex() const {
	return currFrameIndex;
}

double StreamFactory::getFps() const {
	return fps;
}

unsigned int StreamFactory::getDpf() const {
	return uint(dpf*1000);
}